184

     # UI | 03 | Playwright: взаимодействие с элементами

В данной статье будет представлен лишь обзор / поверхностная информация имеющихся во фреймворке методом для работы с элементами:


Поиск элемента

В данной главе представлены методы, с помощью которых можно найти элементы.


get_by_alt_text()

  • Применение:
# регистронезависимый поиск
page.get_by_alt_text("расшифровка ls-вывода")

# регистрозависимый поиск
page.get_by_alt_text("расшифровка ls-вывода", exact=True)

Является аналогом для:

page.locator("//*[@alt='расшифровка ls-вывода']")

get_by_label()

  • Применение:
# регистронезависимый поиск
page.get_by_label("some_label")

# регистрозависимый поиск
page.get_by_label("some_label", exact=True)

Является аналогом для:

page.locator('//*[contains(@aria-label, "some_label")]')
page.locator('//label[contains(text(), "some_label")]')

get_by_placeholder()

  • Применение:
<input placeholder="местодержатель"/>

# регистронезависимый поиск
page.get_by_placeholder("стодерж")

# регистрозависимый поиск
page.get_by_placeholder("стодерж", exact=True)

Является аналогом для:

page.locator('//*[contains(@placeholder, "стодерж")]')

get_by_role()

Описание ролей описано тут. get_by_role() принимает далеко не все значения из этого списка, так как у функции имеется свой - более ограниченный - список.

Пример ролей:

  • button - <button><button>
  • checkbox - <input type=checkbox/>
  • heading - <h1></h1>
  • img - <img src=...>
  • link - <a href=...></a>

["alert", "alertdialog", "application", "article", "banner", "blockquote", "button", "caption", "cell", "checkbox", "code", "columnheader", "combobox", "complementary", "contentinfo", "definition", "deletion", "dialog", "directory", "document", "emphasis", "feed", "figure", "form", "generic", "grid", "gridcell", "group", "heading", "img", "insertion", "link", "list", "listbox", "listitem", "log", "main", "marquee", "math", "menu", "menubar", "menuitem", "menuitemcheckbox", "menuitemradio", "meter", "navigation", "none", "note", "option", "paragraph", "presentation", "progressbar", "radio", "radiogroup", "region", "row", "rowgroup", "rowheader", "scrollbar", "search", "searchbox", "separator", "slider", "spinbutton", "status", "strong", "subscript", "superscript", "switch", "tab", "table", "tablist", "tabpanel", "term", "textbox", "time", "timer", "toolbar", "tooltip", "tree", "treegrid", "treeitem"]

Помимо самих ролей имеются ещё и дополнительные / необязательные параметры, позволяющие уточнить запрос:

  • checked: typing.Optional[bool] = None,
  • disabled: typing.Optional[bool] = None,
  • expanded: typing.Optional[bool] = None,
  • include_hidden: typing.Optional[bool] = None,
  • level: typing.Optional[int] = None,
  • name: typing.Optional[typing.Union[str, typing.Pattern[str]]] = None,
  • pressed: typing.Optional[bool] = None,
  • selected: typing.Optional[bool] = None,
  • exact: typing.Optional[bool] = None

Пример применения:

#найти любой h* с текстом 'пример'
<h3>пример</h3>

page.get_by_role("heading", name="пример")

# регистронезависимый
page.get_by_role("heading", name=re.compile("пример", re.IGNORECASE))

get_by_text()

Делает, что написано в названии. Поддерживает regexp, exact=True (см. описание метода).

<div>Hello <span>world</span></div>
page.get_by_text("Hello world")

Из необычного => несмотря на то, что в примере выше оба слова вложены в разные элементы, совпадение всё равно будет обнаружено.


locator()

Ранее уже приводилось много примеров, но не упоминалось, что поддерживает разный синтаксис:

#xpath
page.locator('//p[text()="Ищу xpath"]')

#xpath=
page.locator('xpath=//p[text()="Ищу xpath"]')

#css
elem = page.locator('.c1')
print(f"всего элементов: {elem.count()}")
всего элементов: 16

#css=
page.locator('css=.c1')

Данная функция также может принимать на вход дополнительные параметры:

  • has_text - ищет текст, содержащийся по частям или полностью в текущем и/или дочерних элементах, регистронезависим
  • has_not_text - то же самое, но проверка осуществляется на отсутствие указанного текста
  • has - ищет локаторы (а не текст) внутри дочерних элементов
  • has_not - проверяет отсутствие указанного локатора в дочерних элементах

Тем самым - помимо "прямого" использования xpath, css (с или без псевдолокаторов) - можно прокидывать дополнительные условия через данные аргументы. Но важно помнить, что эти методы могут использоваться только для проверки и никак не изменяют положение селектора.

Поэтому, если требуется, напр., кликнуть по какому-либо элементу, то требуется корректно указать его путь в первом аргументе (как показано в примерах выше).


has_text

Элементы class="c1" с текстом "прав не выполнение":

page.goto("https://g-oak.ru/post/Linux|Права|theory_permissions")
page.locator('css=.c1', has_text="прав не выполнение")
print(f"{elem.all_inner_texts()}")

['# нет прав не выполнение => rw-']

has_not_text

Элементы class="c1" без буквы "н":

page.goto("https://g-oak.ru/post/Linux|Права|theory_permissions")
elem = page.locator('css=.c1', has_not_text="н")
print(f"всего элементов: {elem.all_inner_texts()}")

всего элементов: [
    '# проверяем текущие прва', 
    '# ожидаемо получаем ошибку', 
    '# сделать владельцем пользователя dao2', 
    '# сделать владельцем пользователя, из-под которого мы сейчас работаем'
]

has

Элемент li, содержащий вложенный элемент <a>

# li содержит текст "а"
page.locator("//li", has=page.locator('text="a"'))

# li содержит локатор "а"
page.locator("//li", has=page.locator('a'))

# li содержит локатор "а" и текст "удаление"
page.locator("//li", has=page.locator('a'), has_text="Удаление")

has_not

# li не содержит локатор "div",
# но содержит текст "удаление"
page.locator("//li", has_not=page.locator('div'), has_text="Удаление")

query_selector()

Отдельно следует подчеркнуть, что согласно документации использование метода query_selector нежелательно.

Если совпадений не найдено, метод возвращает null. Чтобы ждать локатор, требуется дополнительно вызывать wait_for.

По умолчанию может вернуть как одно, так и много элементов, но если использовать strict=True, то - если найдено более одного совпадения - вернёт exception.

Работает как с разными видами локаторов (xpath, css):

page.query_selector('div#some-id')
page.query_selector('//div[@id, "some-id"]')

query_selector_all()

Если нет совпадений возвращает пустой список []:

page.query_selector_all('div#some-id')

Проверка и ожидание состояния элемента


is_checked()

Применим только к radiobutton или checkbox. В противном случае выбрасывает exception. Если в результате запроса находится несколько элементов, то проверяется только первый.

При применении strict - в случае наличия более одного совпадения - выкинет exception.

Можно выставить timeout:

page.is_checked(
    ".checkbox", 
    strict=True, 
    timeout=15000
)

is_disabled()

Является противоположностью к is_enabled. Любой элемент считается enabled, если он:

  • не принадлежит к <button>, <select>, <input> или <textarea>
  • и при этом у него нет свойства disabled

Если в результате запроса находится несколько элементов, то проверяется только первый.

При применении strict - в случае наличия более одного совпадения - выкинет exception.

Можно выставить timeout:

page.is_disabled(
    ".checkbox", 
    strict=True, 
    timeout=15000
)

is_editable()

Любой элемент считается is_editable, если он:

  • является enabled
  • и при этом у него нет свойства readonly

Если в результате запроса находится несколько элементов, то проверяется только первый.

При применении strict - в случае наличия более одного совпадения - выкинет exception.

Можно выставить timeout:

page.is_editable(
    ".checkbox", 
    strict=True, 
    timeout=15000
)

is_enabled()

См. is_disabled

page.is_enabled(
    ".checkbox", 
    strict=True, 
    timeout=15000
)

is_hidden()

Является противоположностью к is_visible. Любой элемент считается hidden, если он:

  • имеет нулевой размер (является "пустым")
  • имеет свойство display:none

Важно: элементы с opacity:0 считаются видимыми

Если в результате запроса находится несколько элементов, то проверяется только первый.

При применении strict - в случае наличия более одного совпадения - выкинет exception.

Можно выставить timeout:

page.is_hidden(
    ".checkbox", 
    strict=True, 
    timeout=15000
)

is_visible()

См. is_hidden

page.is_visible(
    ".checkbox", 
    strict=True, 
    timeout=15000
)

wait_for_selector()

Данный метод описывался в статье "# UI | 01 | Playwright: об ожиданиях"


Обработка нажатий

Какие автоматически проверки используются при вызове того или иного метода "нажатий", подробно описано в документации.


clear()

Применим только к элементам типа <input>, <textarea> или [contenteditable]. Проверят, что элемент visible, editable, enabled, после чего очищает поле.

page.locator('//input[@id="current_page"]')

click()

Поддерживает большое количество действий:

  • выбор нажатия кнопок мышек: левая, правая, колесо
  • дополнительное нажатие клавиш клавиатуры
  • передача координат для клика
  • количество кликов
  • таймаут
  • псевдоклик
page.locator('//div[@id="previous_page"]').click()

См. документацию


drag_to()

Данный метод может использоваться для перемещения какого-либо элемента на странице по его локатору:

page.locator('перетаскиваемый').drag_to(page.locator('куда_бросить'));

То же самое можно осуществить следующим образом:

page.locator('перетаскиваемый').hover();
page.mouse.down();
page.locator('куда_бросить').hover();
page.mouse.up();

fill()

Вставка тексте. Применим к элементам типа: <input>, <textarea> или [contenteditable].

page.locator('//input[@id="current_page"]').fill("0")

См. документацию


input_value()

Возвращает значение value для элементов типа: <input> or <textarea>, <select>, *<label>

current_value = page.locator("...").input_value()
available_values = page.locator("...").input_values()

press()

Данный метод может применяться к разным объектам:

  • Locator
  • Page
  • Keyboard
  • Frame
  • ElementHandle

Здесь рассматривается применение только к локатору.

page.get_by_role("textbox").press("Backspace")

См. документацию


press_sequentially

В большинстве случаев нет необходимости использовать этот метод, так как существует аналогичный: locator.fill("hello")

# печать с задержкой
locator.press_sequentially("пример", delay=100)

См. документацию


tap()

То же самое, что click(), поддерживает те же аргументы. Симулирует касание экрана. Чтобы пользоваться данным методом, контекст браузера должен иметь опцию hasTouch=True.

См. документацию


Работа с клавиатурой

  • keyboard.down()
  • keyboard.up()
  • keyboard.insert_text()
  • keyboard.press()
  • keyboard.release()
  • keyboard.type()

Клавиши: F1 - F12, Digit0- Digit9, KeyA- KeyZ, Backquote, Minus, Equal, Backslash, Backspace, Tab, Delete, Escape, ArrowDown, End, Enter, Home, Insert, PageDown, PageUp, ArrowRight, ArrowUp


keyboard.down()

# ввести текст 523
page.keyboard.down("Digit5")
page.keyboard.down("Digit2")
page.keyboard.down("Digit3")

keyboard.up()

# ввести текст: aABb
page.keyboard.down("KeyA")
page.keyboard.down("Shift")
page.keyboard.down("KeyA")
page.keyboard.down("KeyB")
page.keyboard.up("Shift")
page.keyboard.down("KeyB")

keyboard.insert_text()

page.keyboard.insert_text("Так проще вставить текст")

См. документацию


keyboard.press()

Вместо этого методоа рекомендуется использовать locator.press().

# Ввести текст "Oa"

page.keyboard.press("a")
page.keyboard.press("ArrowLeft")
page.keyboard.press("Shift+O")

keyboard.type()

Имеются более предпочтительные аналоги: locator.fill(), locator.press_sequentially()

page.keyboard.type("World", delay=100)

См. документацию


Работа с мышкой

  • mouse.click()
  • mouse.dblclick()
  • mouse.down()
  • mouse.move()
  • mouse.up()
  • mouse.wheel()

Пример:

page.mouse.click(200, 300)
page.mouse.click(x=400, y=500)

См. документацию


focus()

Устанавливает фокус на html-элементе, то есть, данный элемент будет по умолчанию принимать события клавиатуры и пр.

pagination_field = page.locator('//input[@id="current_page"]')
pagination_field.focus()

page.keyboard.press("a")
page.keyboard.press("ArrowLeft")
page.keyboard.press("Shift+O")

pagination_field.scroll_into_view_if_needed()

События клавиатуры отправляются в поле pagination_field, так как фокус выставлен на нём.

Метод focus() поддерживает параметр timeout.


hover()

Данный метод наводит "курсор мышки" на указанный локатор. Имеет свою логику:

  • выполняет определённые условия ожидания
  • прокручивает страницу до элемента, елси необходимо
  • использует page.mouse для изменения положения курсора

Поддерживает параметры:

  • modifiers
  • position
  • timeout
  • no_wait_after
  • force
  • trial

См. документацию


Обращение к элементам


all()

Сразу возвращает найденные элементы. Если требуется ожидание, надо применять его дополнительно. Данный элемент применяется только к объектам типа локатор и возвращает список всех найденных элементов. Напр.:

page.locator("/div").all()
page.get_by_role("header").all()

all_inner_texts()

Возвращает список с элементами типа текст. Говоря на языке html, обращается к node.innerText, где под node подразумевается какой-либо html-элемент.

page.goto("http://g-oak.ru/post/Linux|Права|theory_permissions")
elem = page.get_by_role("link").all_inner_texts()
print(elem)

['Введение', 'Владелец, группа, Остальные', 
'Права доступа к файлам и каталогам', 
'Информация о файле в системе', 
'Особенности чтения информации о директории']

Не стоит применять данный метод для проверки текста на соответствие, так как для такой цели имеется самостоятельная фнукция to_have_text().


all_text_contents()

Возвращает список с элементами типа текст. Говоря на языке html, обращается к node.textContent, где под node подразумевается какой-либо html-элемент.

page.goto("http://g-oak.ru/post/Linux|Права|theory_permissions")
elem = page.get_by_role("link").all_text_contents()
print(elem)

['Введение', 'Владелец, группа, Остальные', 
'Права доступа к файлам и каталогам', 
'Информация о файле в системе', 
'Особенности чтения информации о директории']

Не стоит применять данный метод для проверки текста на соответствие, так как для такой цели имеется самостоятельная фнукция to_have_text().


count()

Применим к элементам типа локатор, т. е. возвращаемый элемент должен быть именно объектом типа локатор (а не, напр., списком).

elem = page.locator("//li").count()
print(elem)

Не стоит применять данный метод для проверки на соответствие (ассерт-ов), так как для такой цели имеется самостоятельная фнукция to_have_count().


filter()

Имеет те же доп. параметры по работе с текстом, что и метод locator():

  • has_text
  • has_not_text
  • has
  • has_not

Данная функция может быть использована "в цепочке" для получения финального результата:

row_locator = page.locator("tr")
row_locator.filter(
    has_text="text in column 1").filter(
    has=page.get_by_role("button", name="column 2 button")
    ).screenshot()

first

Применяется к локаторам, возвращает первый подходящий элемент.

elem = page.locator("//li").first

Вернёт первый элемент списка.


get_attribute()

Позволяет получить значение атрибута. На вход подаётся название атрибута, значение которого требуется прочитать. Метод также поддерживает timeout.

<h4 id="_4">Группа</h4>

elem = page.locator(
    "//h4[text()='Группа']").get_attribute(
        "id", timeout=15000)

_4

Не стоит применять данный метод для проверки на соответствие (ассерт-ов), так как для такой цели имеется самостоятельная фнукция to_have_attribute().


inner_html()

Говоря на языке html, обращается к element.inner_html, где под element подразумевается какой-либо html-элемент.

  • отображаем внутренние элементы для ul
<ul>
    <li><strong>владельцу файла</strong></li>
    <li><strong>группе, к которой относится файл</strong></li>
    <li><strong>остальным</strong></li>
</ul>

elem = page.locator(
    '//*[contains(text(), "группе, к которой относится файл")]/../..').inner_html()
print(elem)

Внутренними html-элементами ul являются:

<li><strong>владельцу файла</strong></li>
<li><strong>группе, к которой относится файл</strong></li>
<li><strong>остальным</strong></li>

inner_text()

Возвращает внутренний текст html-элемента

page.locator("//label[@id='index_title']").inner_text()

Имеет дополнительный параметр timeout, возвращает объект типа str.


last

Применяется к локаторам, возвращает последний элемент.

elem = page.locator("//li").last

text_content()

Возвращает элемент типа текст. Говоря на языке html, обращается к node.textContent, где под node подразумевается какой-либо html-элемент.

Не стоит применять данный метод для проверки текста на соответствие, так как для такой цели имеется самостоятельная фнукция to_have_text().


Изменение состояния элемента


check()

Используется для активации чекбокса или радиокнопки. Если элемент уже выбран, то просто прекращается выполнение.

page.get_by_role("checkbox").check()

Поддерживает параметры:

  • position
  • timeout
  • force
  • no_wait_after
  • trial

highlight()

Используется применительно к локаторам. Может быть полезен, напр., для дебага, так как выделяет на стринце текущий локатор, а также прописывает его путь.

Но стоит помнить, что применять его следует только для дебагинга и не использовать в "живом" коде:

Highlight the corresponding element(s) on the screen. Useful for debugging, don't commit the code that uses
locator.highlight().

select_option()

Выбор опции / опций внутри элемента типа <select>.

См. документацию


select_text()

Позволяет выбрать текст - в смысле выделить весь текст - внутри элемента. Имеет собственную логику.

См. документацию


set_checked()

В чём-то схож с check(). Используется для де-/активации чекбокса или радиокнопки.

page.get_by_role("checkbox").set_checked(True)

См. документацию


uncheck()

Противоположность методу check(). Если радиокнопка или чекбокс уже деактивированы, то просто прекратит своё выполнение.

См. документацию


Ожидания vs expect

В Playwright имеется огромное количество ожиданий - в плане - что мы ждём от поведения элемента.

from playwright.sync_api import sync_playwright, expect

expect(page.get_by_role("heading", name="пример")).to_be_visible()

Список методов:

  • .to_be_attached()
  • .to_be_checked()
  • .to_be_disabled()
  • .to_be_editable()
  • .to_be_empty()
  • .to_be_enabled()
  • .to_be_focused()
  • .to_be_hidden()
  • .to_be_in_viewport()
  • .to_be_visible()

У каждого из этих методов имеется противоположность вида: .not_* (напр., .not_to_be_attached())

Также имеются методы проверок из серии to_have_* и not_to_have_*, напр.:

  • .to_have_attribute()
  • .to_have_class()
  • .to_have_count()
  • .to_have_css()
  • .to_have_id()
  • .to_have_js_property()
  • .to_have_text()
  • .to_have_value()
  • .to_have_values()

См. документацию